(function() {

  var codeGenerator = (typeof eval("(function () {})") == "function") ?
    function(code) {
      return code;
    } :
    function(code) {
      return "false || " + code;
    };

  // support string type only.
  var stringify = (typeof JSON !== "undefined" && JSON.stringify) ?
    function(s) {
      return JSON.stringify(s);
    } :
    (function() {
      // Implementation comes from JSON2 (/www.json.org/js.html)

      var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

      var meta = { // table of character substitutions
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"': '\\"',
        '\\': '\\\\'
      }

      return function(s) {
        // If the string contains no control characters, no quote characters, and no
        // backslash characters, then we can safely slap some quotes around it.
        // Otherwise we must also replace the offending characters with safe escape
        // sequences.

        escapable.lastIndex = 0;
        return escapable.test(s) ? '"' + s.replace(escapable, function(a) {
          var c = meta[a];
          return typeof c === 's' ? c :
            '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + s + '"';
      };
    })();

  // seed defined in global
  if (typeof __jscex__tempVarSeed === "undefined") {
    __jscex__tempVarSeed = 0;
  }

  var init = function(root) {

    if (root.modules["jit"]) {
      return;
    }

    function JscexTreeGenerator(binder) {
      this._binder = binder;
      this._root = null;
    }
    JscexTreeGenerator.prototype = {

      generate: function(ast) {

        var params = ast[2],
          statements = ast[3];

        this._root = {
          type: "delay",
          stmts: []
        };

        this._visitStatements(statements, this._root.stmts);

        return this._root;
      },

      _getBindInfo: function(stmt) {

        var type = stmt[0];
        if (type == "stat") {
          var expr = stmt[1];
          if (expr[0] == "call") {
            var callee = expr[1];
            if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
              return {
                expression: expr[2][0],
                argName: "",
                assignee: null
              };
            }
          } else if (expr[0] == "assign") {
            var assignee = expr[2];
            expr = expr[3];
            if (expr[0] == "call") {
              var callee = expr[1];
              if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
                return {
                  expression: expr[2][0],
                  argName: "$$_result_$$",
                  assignee: assignee
                };
              }
            }
          }
        } else if (type == "var") {
          var defs = stmt[1];
          if (defs.length == 1) {
            var item = defs[0];
            var name = item[0];
            var expr = item[1];
            if (expr && expr[0] == "call") {
              var callee = expr[1];
              if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
                return {
                  expression: expr[2][0],
                  argName: name,
                  assignee: null
                };
              }
            }
          }
        } else if (type == "return") {
          var expr = stmt[1];
          if (expr && expr[0] == "call") {
            var callee = expr[1];
            if (callee[0] == "name" && callee[1] == this._binder && expr[2].length == 1) {
              return {
                expression: expr[2][0],
                argName: "$$_result_$$",
                assignee: "return"
              };
            }
          }
        }

        return null;
      },

      _visitStatements: function(statements, stmts, index) {
        if (arguments.length <= 2) index = 0;

        if (index >= statements.length) {
          stmts.push({
            type: "normal"
          });
          return this;
        }

        var currStmt = statements[index];
        var bindInfo = this._getBindInfo(currStmt);

        if (bindInfo) {
          var bindStmt = {
            type: "bind",
            info: bindInfo
          };
          stmts.push(bindStmt);

          if (bindInfo.assignee != "return") {
            bindStmt.stmts = [];
            this._visitStatements(statements, bindStmt.stmts, index + 1);
          }

        } else {
          var type = currStmt[0];
          if (type == "return" || type == "break" || type == "continue" || type == "throw") {

            stmts.push({
              type: type,
              stmt: currStmt
            });

          } else if (type == "if" || type == "try" || type == "for" || type == "do" || type == "while" || type == "switch" || type == "for-in") {

            var newStmt = this._visit(currStmt);

            if (newStmt.type == "raw") {
              stmts.push(newStmt);
              this._visitStatements(statements, stmts, index + 1);
            } else {
              var isLast = (index == statements.length - 1);
              if (isLast) {
                stmts.push(newStmt);
              } else {

                var combineStmt = {
                  type: "combine",
                  first: {
                    type: "delay",
                    stmts: [newStmt]
                  },
                  second: {
                    type: "delay",
                    stmts: []
                  }
                };
                stmts.push(combineStmt);

                this._visitStatements(statements, combineStmt.second.stmts, index + 1);
              }
            }

          } else {

            stmts.push({
              type: "raw",
              stmt: currStmt
            });

            this._visitStatements(statements, stmts, index + 1);
          }
        }

        return this;
      },

      _visit: function(ast) {

        var type = ast[0];

        function throwUnsupportedError() {
          throw new Error('"' + type + '" is not currently supported.');
        }

        var visitor = this._visitors[type];

        if (visitor) {
          return visitor.call(this, ast);
        } else {
          throwUnsupportedError();
        }
      },

      _visitBody: function(ast, stmts) {
        if (ast[0] == "block") {
          this._visitStatements(ast[1], stmts);
        } else {
          this._visitStatements([ast], stmts);
        }
      },

      _noBinding: function(stmts) {
        switch (stmts[stmts.length - 1].type) {
          case "normal":
          case "return":
          case "break":
          case "throw":
          case "continue":
            return true;
        }

        return false;
      },

      _collectCaseStatements: function(cases, index) {
        var res = [];

        for (var i = index; i < cases.length; i++) {
          var rawStmts = cases[i][1];
          for (var j = 0; j < rawStmts.length; j++) {
            if (rawStmts[j][0] == "break") {
              return res
            }

            res.push(rawStmts[j]);
          }
        }

        return res;
      },

      _visitors: {

        "for": function(ast) {

          var bodyStmts = [];
          var body = ast[4];
          this._visitBody(body, bodyStmts);

          if (this._noBinding(bodyStmts)) {
            return {
              type: "raw",
              stmt: ast
            };
          }

          var delayStmt = {
            type: "delay",
            stmts: []
          };

          var setup = ast[1];
          if (setup) {
            delayStmt.stmts.push({
              type: "raw",
              stmt: setup
            });
          }

          var loopStmt = {
            type: "loop",
            bodyFirst: false,
            bodyStmt: {
              type: "delay",
              stmts: bodyStmts
            }
          };
          delayStmt.stmts.push(loopStmt);

          var condition = ast[2];
          if (condition) {
            loopStmt.condition = condition;
          }

          var update = ast[3];
          if (update) {
            loopStmt.update = update;
          }

          return delayStmt;
        },

        "for-in": function(ast) {

          var body = ast[4];

          var bodyStmts = [];
          this._visitBody(body, bodyStmts);

          if (this._noBinding(bodyStmts)) {
            return {
              type: "raw",
              stmt: ast
            };
          }

          var id = (__jscex__tempVarSeed++);
          var keysVar = "$$_keys_$$_" + id;
          var indexVar = "$$_index_$$_" + id;
          // var memVar = "$$_mem_$$_" + id;

          var delayStmt = {
            type: "delay",
            stmts: []
          };

          // var members = Jscex._forInKeys(obj);
          var keysAst = root.parse("var " + keysVar + " = Jscex._forInKeys(obj);")[1][0];
          keysAst[1][0][1][2][0] = ast[3]; // replace obj with real AST;
          delayStmt.stmts.push({
            type: "raw",
            stmt: keysAst
          });

          /*
          // var members = [];
          delayStmt.stmts.push({
              type: "raw",
              stmt: uglifyJS.parse("var " + membersVar + " = [];")[1][0]
          });

          // for (var mem in obj) members.push(mem);
          var keysAst = uglifyJS.parse("for (var " + memVar +" in obj) " + membersVar + ".push(" + memVar + ");")[1][0];
          keysAst[3] = ast[3]; // replace the "obj" with real AST.
          delayStmt.stmts.push({ type : "raw", stmt: keysAst});
          */

          // var index = 0;
          delayStmt.stmts.push({
            type: "raw",
            stmt: root.parse("var " + indexVar + " = 0;")[1][0]
          });

          // index < members.length
          var condition = root.parse(indexVar + " < " + keysVar + ".length")[1][0][1];

          // index++
          var update = root.parse(indexVar + "++")[1][0][1];

          var loopStmt = {
            type: "loop",
            bodyFirst: false,
            update: update,
            condition: condition,
            bodyStmt: {
              type: "delay",
              stmts: []
            }
          };
          delayStmt.stmts.push(loopStmt);

          var varName = ast[2][1]; // ast[2] == ["name", m]
          if (ast[1][0] == "var") {
            loopStmt.bodyStmt.stmts.push({
              type: "raw",
              stmt: root.parse("var " + varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
            });
          } else {
            loopStmt.bodyStmt.stmts.push({
              type: "raw",
              stmt: root.parse(varName + " = " + keysVar + "[" + indexVar + "];")[1][0]
            });
          }

          this._visitBody(body, loopStmt.bodyStmt.stmts);

          return delayStmt;
        },

        "while": function(ast) {

          var bodyStmts = [];
          var body = ast[2];
          this._visitBody(body, bodyStmts);

          if (this._noBinding(bodyStmts)) {
            return {
              type: "raw",
              stmt: ast
            }
          }

          var loopStmt = {
            type: "loop",
            bodyFirst: false,
            bodyStmt: {
              type: "delay",
              stmts: bodyStmts
            }
          };

          var condition = ast[1];
          loopStmt.condition = condition;

          return loopStmt;
        },

        "do": function(ast) {

          var bodyStmts = [];
          var body = ast[2];
          this._visitBody(body, bodyStmts);

          if (this._noBinding(bodyStmts)) {
            return {
              type: "raw",
              stmt: ast
            };
          }

          var loopStmt = {
            type: "loop",
            bodyFirst: true,
            bodyStmt: {
              type: "delay",
              stmts: bodyStmts
            }
          };

          var condition = ast[1];
          loopStmt.condition = condition;

          return loopStmt;
        },

        "switch": function(ast) {
          var noBinding = true;

          var switchStmt = {
            type: "switch",
            item: ast[1],
            caseStmts: []
          };

          var cases = ast[2];
          for (var i = 0; i < cases.length; i++) {
            var caseStmt = {
              item: cases[i][0],
              stmts: []
            };
            switchStmt.caseStmts.push(caseStmt);

            var statements = this._collectCaseStatements(cases, i);
            this._visitStatements(statements, caseStmt.stmts);
            noBinding = noBinding && this._noBinding(caseStmt.stmts);
          }

          if (noBinding) {
            return {
              type: "raw",
              stmt: ast
            };
          } else {
            return switchStmt;
          }
        },

        "if": function(ast) {

          var noBinding = true;

          var ifStmt = {
            type: "if",
            conditionStmts: []
          };

          var currAst = ast;
          while (true) {
            var condition = currAst[1];
            var condStmt = {
              cond: condition,
              stmts: []
            };
            ifStmt.conditionStmts.push(condStmt);

            var thenPart = currAst[2];
            this._visitBody(thenPart, condStmt.stmts);

            noBinding = noBinding && this._noBinding(condStmt.stmts);

            var elsePart = currAst[3];
            if (elsePart && elsePart[0] == "if") {
              currAst = elsePart;
            } else {
              break;
            }
          }

          var elsePart = currAst[3];
          if (elsePart) {
            ifStmt.elseStmts = [];

            this._visitBody(elsePart, ifStmt.elseStmts);

            noBinding = noBinding && this._noBinding(ifStmt.elseStmts);
          }

          if (noBinding) {
            return {
              type: "raw",
              stmt: ast
            };
          } else {
            return ifStmt;
          }
        },

        "try": function(ast, stmts) {

          var bodyStmts = [];
          var bodyStatements = ast[1];
          this._visitStatements(bodyStatements, bodyStmts);

          var noBinding = this._noBinding(bodyStmts)

          var tryStmt = {
            type: "try",
            bodyStmt: {
              type: "delay",
              stmts: bodyStmts
            }
          };

          var catchClause = ast[2];
          if (catchClause) {
            var exVar = catchClause[0];
            tryStmt.exVar = exVar;
            tryStmt.catchStmts = [];

            this._visitStatements(catchClause[1], tryStmt.catchStmts);

            noBinding = noBinding && this._noBinding(tryStmt.catchStmts);
          }

          var finallyStatements = ast[3];
          if (finallyStatements) {
            tryStmt.finallyStmt = {
              type: "delay",
              stmts: []
            };

            this._visitStatements(finallyStatements, tryStmt.finallyStmt.stmts);

            noBinding = noBinding && this._noBinding(tryStmt.finallyStmt.stmts);
          }

          if (noBinding) {
            return {
              type: "raw",
              stmt: ast
            };
          } else {
            return tryStmt;
          }
        }
      }
    }

    function CodeGenerator(builderName, binder, indent) {
      this._builderName = builderName;
      this._binder = binder;
      this._normalMode = false;
      this._indent = indent;
      this._indentLevel = 0;
      this._builderVar = "$$_builder_$$_" + (__jscex__tempVarSeed++);
    }
    CodeGenerator.prototype = {
      _write: function(s) {
        this._buffer.push(s);
        return this;
      },

      _writeLine: function(s) {
        this._write(s)._write("\n");
        return this;
      },

      _writeIndents: function() {
        for (var i = 0; i < this._indent; i++) {
          this._write(" ");
        }

        for (var i = 0; i < this._indentLevel; i++) {
          this._write("    ");
        }
        return this;
      },

      generate: function(params, jscexAst) {
        this._buffer = [];

        this._writeLine("(function (" + params.join(", ") + ") {");
        this._indentLevel++;

        this._writeIndents()
          ._writeLine("var " + this._builderVar + " = Jscex.builders[" + stringify(this._builderName) + "];");

        this._writeIndents()
          ._writeLine("return " + this._builderVar + ".Start(this,");
        this._indentLevel++;

        this._pos = {};

        this._writeIndents()
          ._visitJscex(jscexAst)
          ._writeLine();
        this._indentLevel--;

        this._writeIndents()
          ._writeLine(");");
        this._indentLevel--;

        this._writeIndents()
          ._write("})");

        return this._buffer.join("");
      },

      _visitJscex: function(ast) {
        this._jscexVisitors[ast.type].call(this, ast);
        return this;
      },

      _visitRaw: function(ast) {
        var type = ast[0];

        function throwUnsupportedError() {
          throw new Error('"' + type + '" is not currently supported.');
        }

        var visitor = this._rawVisitors[type];

        if (visitor) {
          visitor.call(this, ast);
        } else {
          throwUnsupportedError();
        }

        return this;
      },

      _visitJscexStatements: function(statements) {
        for (var i = 0; i < statements.length; i++) {
          var stmt = statements[i];

          if (stmt.type == "raw" || stmt.type == "if" || stmt.type == "switch") {
            this._writeIndents()
              ._visitJscex(stmt)._writeLine();
          } else if (stmt.type == "delay") {
            this._visitJscexStatements(stmt.stmts);
          } else {
            this._writeIndents()
              ._write("return ")._visitJscex(stmt)._writeLine(";");
          }
        }
      },

      _visitRawStatements: function(statements) {
        for (var i = 0; i < statements.length; i++) {
          var s = statements[i];

          this._writeIndents()
            ._visitRaw(s)._writeLine();

          switch (s[0]) {
            case "break":
            case "return":
            case "continue":
            case "throw":
              return;
          }
        }
      },

      _visitRawBody: function(body) {
        if (body[0] == "block") {
          this._visitRaw(body);
        } else {
          this._writeLine();
          this._indentLevel++;

          this._writeIndents()
            ._visitRaw(body);
          this._indentLevel--;
        }

        return this;
      },

      _visitRawFunction: function(ast) {
        var funcName = ast[1] || "";
        var args = ast[2];
        var statements = ast[3];

        this._writeLine("function " + funcName + "(" + args.join(", ") + ") {")
        this._indentLevel++;

        var currInFunction = this._pos.inFunction;
        this._pos.inFunction = true;

        this._visitRawStatements(statements);
        this._indentLevel--;

        this._pos.inFunction = currInFunction;

        this._writeIndents()
          ._write("}");
      },

      _jscexVisitors: {
        "delay": function(ast) {
          if (ast.stmts.length == 1) {
            var subStmt = ast.stmts[0];
            switch (subStmt.type) {
              case "delay":
              case "combine":
              case "normal":
              case "break":
              case "continue":
              case "loop":
              case "try":
                this._visitJscex(subStmt);
                return;
              case "return":
                if (!subStmt.stmt[1]) {
                  this._visitJscex(subStmt);
                  return;
                }
            }
          }

          this._writeLine(this._builderVar + ".Delay(function () {");
          this._indentLevel++;

          this._visitJscexStatements(ast.stmts);
          this._indentLevel--;

          this._writeIndents()
            ._write("})");
        },

        "combine": function(ast) {
          this._writeLine(this._builderVar + ".Combine(");
          this._indentLevel++;

          this._writeIndents()
            ._visitJscex(ast.first)._writeLine(",");
          this._writeIndents()
            ._visitJscex(ast.second)._writeLine();
          this._indentLevel--;

          this._writeIndents()
            ._write(")");
        },

        "loop": function(ast) {
          this._writeLine(this._builderVar + ".Loop(");
          this._indentLevel++;

          if (ast.condition) {
            this._writeIndents()
              ._writeLine("function () {");
            this._indentLevel++;

            this._writeIndents()
              ._write("return ")._visitRaw(ast.condition)._writeLine(";");
            this._indentLevel--;

            this._writeIndents()
              ._writeLine("},");
          } else {
            this._writeIndents()._writeLine("null,");
          }

          if (ast.update) {
            this._writeIndents()
              ._writeLine("function () {");
            this._indentLevel++;

            this._writeIndents()
              ._visitRaw(ast.update)._writeLine(";");
            this._indentLevel--;

            this._writeIndents()
              ._writeLine("},");
          } else {
            this._writeIndents()._writeLine("null,");
          }

          this._writeIndents()
            ._visitJscex(ast.bodyStmt)._writeLine(",");

          this._writeIndents()
            ._writeLine(ast.bodyFirst);
          this._indentLevel--;

          this._writeIndents()
            ._write(")");
        },

        "raw": function(ast) {
          this._visitRaw(ast.stmt);
        },

        "bind": function(ast) {
          var info = ast.info;
          this._write(this._builderVar + ".Bind(")._visitRaw(info.expression)._writeLine(", function (" + info.argName + ") {");
          this._indentLevel++;

          if (info.assignee == "return") {
            this._writeIndents()
              ._writeLine("return " + this._builderVar + ".Return(" + info.argName + ");");
          } else {
            if (info.assignee) {
              this._writeIndents()
                ._visitRaw(info.assignee)._writeLine(" = " + info.argName + ";");
            }

            this._visitJscexStatements(ast.stmts);
          }
          this._indentLevel--;

          this._writeIndents()
            ._write("})");
        },

        "if": function(ast) {

          for (var i = 0; i < ast.conditionStmts.length; i++) {
            var stmt = ast.conditionStmts[i];

            this._write("if (")._visitRaw(stmt.cond)._writeLine(") {");
            this._indentLevel++;

            this._visitJscexStatements(stmt.stmts);
            this._indentLevel--;

            this._writeIndents()
              ._write("} else ");
          }

          this._writeLine("{");
          this._indentLevel++;

          if (ast.elseStmts) {
            this._visitJscexStatements(ast.elseStmts);
          } else {
            this._writeIndents()
              ._writeLine("return " + this._builderVar + ".Normal();");
          }

          this._indentLevel--;

          this._writeIndents()
            ._write("}");
        },

        "switch": function(ast) {
          this._write("switch (")._visitRaw(ast.item)._writeLine(") {");
          this._indentLevel++;

          for (var i = 0; i < ast.caseStmts.length; i++) {
            var caseStmt = ast.caseStmts[i];

            if (caseStmt.item) {
              this._writeIndents()
                ._write("case ")._visitRaw(caseStmt.item)._writeLine(":");
            } else {
              this._writeIndents()._writeLine("default:");
            }
            this._indentLevel++;

            this._visitJscexStatements(caseStmt.stmts);
            this._indentLevel--;
          }

          this._writeIndents()
            ._write("}");
        },

        "try": function(ast) {
          this._writeLine(this._builderVar + ".Try(");
          this._indentLevel++;

          this._writeIndents()
            ._visitJscex(ast.bodyStmt)._writeLine(",");

          if (ast.catchStmts) {
            this._writeIndents()
              ._writeLine("function (" + ast.exVar + ") {");
            this._indentLevel++;

            this._visitJscexStatements(ast.catchStmts);
            this._indentLevel--;

            this._writeIndents()
              ._writeLine("},");
          } else {
            this._writeIndents()
              ._writeLine("null,");
          }

          if (ast.finallyStmt) {
            this._writeIndents()
              ._visitJscex(ast.finallyStmt)._writeLine();
          } else {
            this._writeIndents()
              ._writeLine("null");
          }
          this._indentLevel--;

          this._writeIndents()
            ._write(")");
        },

        "normal": function(ast) {
          this._write(this._builderVar + ".Normal()");
        },

        "throw": function(ast) {
          this._write(this._builderVar + ".Throw(")._visitRaw(ast.stmt[1])._write(")");
        },

        "break": function(ast) {
          this._write(this._builderVar + ".Break()");
        },

        "continue": function(ast) {
          this._write(this._builderVar + ".Continue()");
        },

        "return": function(ast) {
          this._write(this._builderVar + ".Return(");
          if (ast.stmt[1]) this._visitRaw(ast.stmt[1]);
          this._write(")");
        }
      },

      _rawVisitors: {
        "var": function(ast) {
          this._write("var ");

          var items = ast[1];
          for (var i = 0; i < items.length; i++) {
            this._write(items[i][0]);
            if (items[i].length > 1) {
              this._write(" = ")._visitRaw(items[i][1]);
            }
            if (i < items.length - 1) this._write(", ");
          }

          this._write(";");
        },

        "seq": function(ast) {
          for (var i = 1; i < ast.length; i++) {
            this._visitRaw(ast[i]);
            if (i < ast.length - 1) this._write(", ");
          }
        },

        "binary": function(ast) {
          var op = ast[1],
            left = ast[2],
            right = ast[3];

          function needBracket(item) {
            var type = item[0];
            return !(type == "num" || type == "name" || type == "dot");
          }

          if (needBracket(left)) {
            this._write("(")._visitRaw(left)._write(") ");
          } else {
            this._visitRaw(left)._write(" ");
          }

          this._write(op);

          if (needBracket(right)) {
            this._write(" (")._visitRaw(right)._write(")");
          } else {
            this._write(" ")._visitRaw(right);
          }
        },

        "sub": function(ast) {
          var prop = ast[1],
            index = ast[2];

          function needBracket() {
            return !(prop[0] == "name")
          }

          if (needBracket()) {
            this._write("(")._visitRaw(prop)._write(")[")._visitRaw(index)._write("]");
          } else {
            this._visitRaw(prop)._write("[")._visitRaw(index)._write("]");
          }
        },

        "unary-postfix": function(ast) {
          var op = ast[1];
          var item = ast[2];
          this._visitRaw(item)._write(op);
        },

        "unary-prefix": function(ast) {
          var op = ast[1];
          var item = ast[2];
          this._write(op);
          if (op == "typeof") {
            this._write("(")._visitRaw(item)._write(")");
          } else {
            this._visitRaw(item);
          }
        },

        "assign": function(ast) {
          var op = ast[1];
          var name = ast[2];
          var value = ast[3];

          this._visitRaw(name);
          if ((typeof op) == "string") {
            this._write(" " + op + "= ");
          } else {
            this._write(" = ");
          }
          this._visitRaw(value);
        },

        "stat": function(ast) {
          this._visitRaw(ast[1])._write(";");
        },

        "dot": function(ast) {
          function needBracket() {
            var leftOp = ast[1][0];
            return !(leftOp == "dot" || leftOp == "name");
          }

          if (needBracket()) {
            this._write("(")._visitRaw(ast[1])._write(").")._write(ast[2]);
          } else {
            this._visitRaw(ast[1])._write(".")._write(ast[2]);
          }
        },

        "new": function(ast) {
          var ctor = ast[1];

          this._write("new ")._visitRaw(ctor)._write("(");

          var args = ast[2];
          for (var i = 0, len = args.length; i < len; i++) {
            this._visitRaw(args[i]);
            if (i < len - 1) this._write(", ");
          }

          this._write(")");
        },

        "call": function(ast) {

          if (_isJscexPattern(ast)) {
            var indent = this._indent + this._indentLevel * 4;
            var newCode = _compileJscexPattern(ast, indent);
            this._write(newCode);
          } else {

            var invalidBind = (ast[1][0] == "name") && (ast[1][1] == this._binder);
            if (invalidBind) {
              this._pos = {
                inFunction: true
              };
              this._buffer = [];
            }

            this._visitRaw(ast[1])._write("(");

            var args = ast[2];
            for (var i = 0; i < args.length; i++) {
              this._visitRaw(args[i]);
              if (i < args.length - 1) this._write(", ");
            }

            this._write(")");

            if (invalidBind) {
              throw ("Invalid bind operation: " + this._buffer.join(""));
            }
          }
        },

        "name": function(ast) {
          this._write(ast[1]);
        },

        "object": function(ast) {
          var items = ast[1];
          if (items.length <= 0) {
            this._write("{ }");
          } else {
            this._writeLine("{");
            this._indentLevel++;

            for (var i = 0; i < items.length; i++) {
              this._writeIndents()
                ._write(stringify(items[i][0]) + ": ")
                ._visitRaw(items[i][1]);

              if (i < items.length - 1) {
                this._writeLine(",");
              } else {
                this._writeLine("");
              }
            }

            this._indentLevel--;
            this._writeIndents()._write("}");
          }
        },

        "array": function(ast) {
          this._write("[");

          var items = ast[1];
          for (var i = 0; i < items.length; i++) {
            this._visitRaw(items[i]);
            if (i < items.length - 1) this._write(", ");
          }

          this._write("]");
        },

        "num": function(ast) {
          this._write(ast[1]);
        },

        "regexp": function(ast) {
          this._write("/" + ast[1] + "/" + ast[2]);
        },

        "string": function(ast) {
          this._write(stringify(ast[1]));
        },

        "function": function(ast) {
          this._visitRawFunction(ast);
        },

        "defun": function(ast) {
          this._visitRawFunction(ast);
        },

        "return": function(ast) {
          if (this._pos.inFunction) {
            this._write("return");
            var value = ast[1];
            if (value) this._write(" ")._visitRaw(value);
            this._write(";");
          } else {
            this._write("return ")._visitJscex({
              type: "return",
              stmt: ast
            })._write(";");
          }
        },

        "for": function(ast) {
          this._write("for (");

          var setup = ast[1];
          if (setup) {
            this._visitRaw(setup);
            if (setup[0] != "var") {
              this._write("; ");
            } else {
              this._write(" ");
            }
          } else {
            this._write("; ");
          }

          var condition = ast[2];
          if (condition) this._visitRaw(condition);
          this._write("; ");

          var update = ast[3];
          if (update) this._visitRaw(update);
          this._write(") ");

          var currInLoop = this._pos.inLoop;
          this._pos.inLoop = true;

          var body = ast[4];
          this._visitRawBody(body);

          this._pos.inLoop = currInLoop;
        },

        "for-in": function(ast) {
          this._write("for (");

          var declare = ast[1];
          if (declare[0] == "var") { // declare == ["var", [["m"]]]
            this._write("var " + declare[1][0][0]);
          } else {
            this._visitRaw(declare);
          }

          this._write(" in ")._visitRaw(ast[3])._write(") ");

          var body = ast[4];
          this._visitRawBody(body);
        },

        "block": function(ast) {
          this._writeLine("{")
          this._indentLevel++;

          this._visitRawStatements(ast[1]);
          this._indentLevel--;

          this._writeIndents()
            ._write("}");
        },

        "while": function(ast) {
          var condition = ast[1];
          var body = ast[2];

          var currInLoop = this._pos.inLoop
          this._pos.inLoop = true;

          this._write("while (")._visitRaw(condition)._write(") ")._visitRawBody(body);

          this._pos.inLoop = currInLoop;
        },

        "do": function(ast) {
          var condition = ast[1];
          var body = ast[2];

          var currInLoop = this._pos.inLoop;
          this._pos.inLoop = true;

          this._write("do ")._visitRawBody(body);

          this._pos.inLoop = currInLoop;

          if (body[0] == "block") {
            this._write(" ");
          } else {
            this._writeLine()._writeIndents();
          }

          this._write("while (")._visitRaw(condition)._write(");");
        },

        "if": function(ast) {
          var condition = ast[1];
          var thenPart = ast[2];

          this._write("if (")._visitRaw(condition)._write(") ")._visitRawBody(thenPart);

          var elsePart = ast[3];
          if (elsePart) {
            if (thenPart[0] == "block") {
              this._write(" ");
            } else {
              this._writeLine("")
                ._writeIndents();
            }

            if (elsePart[0] == "if") {
              this._write("else ")._visitRaw(elsePart);
            } else {
              this._write("else ")._visitRawBody(elsePart);
            }
          }
        },

        "break": function(ast) {
          if (this._pos.inLoop || this._pos.inSwitch) {
            this._write("break;");
          } else {
            this._write("return ")._visitJscex({
              type: "break",
              stmt: ast
            })._write(";");
          }
        },

        "continue": function(ast) {
          if (this._pos.inLoop) {
            this._write("continue;");
          } else {
            this._write("return ")._visitJscex({
              type: "continue",
              stmt: ast
            })._write(";");
          }
        },

        "throw": function(ast) {
          var pos = this._pos;
          if (pos.inTry || pos.inFunction) {
            this._write("throw ")._visitRaw(ast[1])._write(";");
          } else {
            this._write("return ")._visitJscex({
              type: "throw",
              stmt: ast
            })._write(";");
          }
        },

        "conditional": function(ast) {
          this._write("(")._visitRaw(ast[1])._write(") ? (")._visitRaw(ast[2])._write(") : (")._visitRaw(ast[3])._write(")");
        },

        "try": function(ast) {

          this._writeLine("try {");
          this._indentLevel++;

          var currInTry = this._pos.inTry;
          this._pos.inTry = true;

          this._visitRawStatements(ast[1]);
          this._indentLevel--;

          this._pos.inTry = currInTry;

          var catchClause = ast[2];
          var finallyStatements = ast[3];

          if (catchClause) {
            this._writeIndents()
              ._writeLine("} catch (" + catchClause[0] + ") {")
            this._indentLevel++;

            this._visitRawStatements(catchClause[1]);
            this._indentLevel--;
          }

          if (finallyStatements) {
            this._writeIndents()
              ._writeLine("} finally {");
            this._indentLevel++;

            this._visitRawStatements(finallyStatements);
            this._indentLevel--;
          }

          this._writeIndents()
            ._write("}");
        },

        "switch": function(ast) {
          this._write("switch (")._visitRaw(ast[1])._writeLine(") {");
          this._indentLevel++;

          var currInSwitch = this._pos.inSwitch;
          this._pos.inSwitch = true;

          var cases = ast[2];
          for (var i = 0; i < cases.length; i++) {
            var c = cases[i];
            this._writeIndents();

            if (c[0]) {
              this._write("case ")._visitRaw(c[0])._writeLine(":");
            } else {
              this._writeLine("default:");
            }
            this._indentLevel++;

            this._visitRawStatements(c[1]);
            this._indentLevel--;
          }
          this._indentLevel--;

          this._pos.inSwitch = currInSwitch;

          this._writeIndents()
            ._write("}");
        }
      }
    }

    function _isJscexPattern(ast) {
      if (ast[0] != "call") return false;

      var evalName = ast[1];
      if (evalName[0] != "name" || evalName[1] != "eval") return false;

      var compileCall = ast[2][0];
      if (!compileCall || compileCall[0] != "call") return false;

      var compileMethod = compileCall[1];
      if (!compileMethod || compileMethod[0] != "dot" || compileMethod[2] != "compile") return false;

      var jscexName = compileMethod[1];
      if (!jscexName || jscexName[0] != "name" || jscexName[1] != "Jscex") return false;

      var builder = compileCall[2][0];
      if (!builder || builder[0] != "string") return false;

      var func = compileCall[2][1];
      if (!func || func[0] != "function") return false;

      return true;
    }

    function _compileJscexPattern(ast, indent) {

      var builderName = ast[2][0][2][0][1];
      var funcAst = ast[2][0][2][1];
      var binder = root.binders[builderName];

      var jscexTreeGenerator = new JscexTreeGenerator(binder);
      var jscexAst = jscexTreeGenerator.generate(funcAst);

      var codeGenerator = new CodeGenerator(builderName, binder, indent);
      var newCode = codeGenerator.generate(funcAst[2], jscexAst);

      return newCode;
    }

    function compile(builderName, func) {

      var funcCode = func.toString();
      var evalCode = "eval(Jscex.compile(" + stringify(builderName) + ", " + funcCode + "))"
      var evalCodeAst = root.parse(evalCode);

      // [ "toplevel", [ [ "stat", [ "call", ... ] ] ] ]
      var evalAst = evalCodeAst[1][0][1];
      var newCode = _compileJscexPattern(evalAst, 0);

      root.logger.debug(funcCode + "\n\n>>>\n\n" + newCode);

      return codeGenerator(newCode);
    };

    root.compile = compile;

    root.modules["jit"] = true;
  }

  var isCommonJS = (typeof require !== "undefined" && typeof module !== "undefined" && module.exports);
  var isAmd = (typeof define !== "undefined" && define.amd);

  if (isCommonJS) {
    module.exports.init = function(root) {
      if (!root.modules["parser"]) {
        require("./jscex-parser").init(root);
      };

      init(root);
    }
  } else if (isAmd) {
    define("jscex-jit", ["jscex-parser"], function(parser) {
      return {
        init: function(root) {
          if (!root.modules["parser"]) {
            parser.init(root);
          }

          init(root);
        }
      };
    });
  } else {
    if (typeof Jscex === "undefined") {
      throw new Error('Missing root object, please load "jscex" module first.');
    }

    if (!Jscex.modules["parser"]) {
      throw new Error('Missing essential components, please initialize "parser" module first.');
    }

    init(Jscex);
  }

})();
